﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Security.Principal;
using Wamm.AbstractDataAccess;
using Wamm.ApplicationExceptions;
using Wamm.EntityFrameworkImplementation.EntityConfigurations;
using Wamm.Model.Abstract;
using Wamm.Model.Entities;

namespace Wamm.EntityFrameworkImplementation {
    public class Session : ISession {

        private WammCatalog _wammCatalog;
        private readonly IRepositoryFactory _repositoryFactory;
        private readonly IPrincipalProvider _principalProvider;
        private IRepository<Event> _events;
        private IRepository<EventType> _eventTypes;
        private IRepository<Family> _families;
        private IRepository<FamilyPhoto> _familyPhotos;
        private IRepository<Participant> _participants;
        private IRepository<ParticipantType> _participantTypes;
        private IRepository<Person> _people;
        private IRepository<PersonPhoto> _personPhotos;

        public Session(string connectionString, 
                       IRepositoryFactory repositoryFactory,
                       IPrincipalProvider principalProvider)
        {
            CreateModel<WammCatalog>();
            _wammCatalog = new WammCatalog(connectionString);
            _repositoryFactory = repositoryFactory;
            _principalProvider = principalProvider;
        }

        private static void CreateModel<T>() where T : System.Data.Entity.DbContext
        {
            var builder = new ModelBuilder();
            ConfigureEntities(builder);
#if (DEBUG)
            // Note - integrated security is needed to recreate database if model changes
            Database.SetInitializer(new RecreateDatabaseIfModelChanges<T>());
#else
            Database.SetInitializer<WAMMCatalog>(null);
#endif
            builder.CreateModel();
        }

        private static void ConfigureEntities(ModelBuilder builder)
        {
            builder.Configurations.Add(new EventConfiguration());
            builder.Configurations.Add(new EventTypeConfiguration());
            builder.Configurations.Add(new FamilyConfiguration());
            builder.Configurations.Add(new FamilyPhotoConfiguration());
            builder.Configurations.Add(new ParticipantConfiguration());
            builder.Configurations.Add(new ParticipantTypeConfiguration());
            builder.Configurations.Add(new PersonConfiguration());
            builder.Configurations.Add(new PersonPhotoConfiguration());
        }

        public IPrincipal Principal { get; set; }

        public IRepository<Event> Events {
            get {
                if (_events == null) {
                    _events = _repositoryFactory.Create<Event>(_wammCatalog);
                }
                return _events;
            }
        }

        public IRepository<EventType> EventTypes {
            get {
                if (_eventTypes == null) {
                    _eventTypes = _repositoryFactory.Create<EventType>(_wammCatalog);
                }
                return _eventTypes;
            }
        }

        public IRepository<Family> Families {
            get {
                if (_families == null) {
                    _families = _repositoryFactory.Create<Family>(_wammCatalog);
                }
                return _families;
            }
        }

        public IRepository<FamilyPhoto> FamilyPhotos
        {
            get
            {
                if (_familyPhotos == null)
                {
                    _familyPhotos = _repositoryFactory.Create<FamilyPhoto>(_wammCatalog);
                }
                return _familyPhotos;
            }
        }

        public IRepository<Participant> Participants {
            get {
                if (_participants == null) {
                    _participants = _repositoryFactory.Create<Participant>(_wammCatalog);
                }
                return _participants;
            }
        }

        public IRepository<ParticipantType> ParticipantTypes {
            get {
                if (_participantTypes == null) {
                    _participantTypes = _repositoryFactory.Create<ParticipantType>(_wammCatalog);
                }
                return _participantTypes;
            }
        }

        public IRepository<Person> People {
            get {
                if (_people == null) {
                    _people = _repositoryFactory.Create<Person>(_wammCatalog);
                }
                return _people;
            }
        }

        public IRepository<PersonPhoto> PersonPhotos {
            get {
                if (_personPhotos == null) {
                    _personPhotos = _repositoryFactory.Create<PersonPhoto>(_wammCatalog);
                }
                return _personPhotos;
            }
        }

        public void Commit(IList<RuleViolation> errors) {
            ValidateChanges(errors);
            if (!errors.Any()) 
            {
                UpdateAddedAndOrLastUpdated();
                _wammCatalog.SaveChanges();    
            }
        }

        private void UpdateAddedAndOrLastUpdated() {
            var changedEntities = ChangedEntities<IEntity>();
            foreach (var changedEntity in changedEntities) {
                if (changedEntity.AddedBy == null) {
                    changedEntity.AddedBy = _principalProvider.Principal.Identity.Name;
                    changedEntity.Added = DateTime.Now;
                }
                   
                changedEntity.LastUpdatedBy = _principalProvider.Principal.Identity.Name;
                changedEntity.LastUpdated = DateTime.Now;
            }
        }

        public void Dispose() {
            if (_wammCatalog != null) {
                _wammCatalog.Dispose();
                _wammCatalog = null;
            }
        }

        private void ValidateChanges(IList<RuleViolation> errors) {
            var changedEntities = ChangedEntities<IEntity>();
            foreach (var changedEntity in changedEntities) {
                changedEntity.Validate(errors);
            }
        }

        private IEnumerable<T> ChangedEntities<T>() {
            var changedEntities = _wammCatalog.ObjContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added);
            return changedEntities.Where(entry => entry.Entity is T).Select(entry => (T)entry.Entity);
        }
    }
}
